home *** CD-ROM | disk | FTP | other *** search
/ MacWorld 2000 February / Macworld (2000-02).dmg / Cool Extras! / DarkSide of the Mac 5.0.6 / SampleFaders / Morpion.c < prev    next >
Text File  |  1999-05-19  |  10KB  |  339 lines

  1. /*
  2.     Morpion (French) == 5 In A Row (English)
  3.     The first one who puts 5 markers in a row (horizontal, vertical or diagonal) wins. The computer plays against itself.
  4.     
  5.     This small fader is intended as a code example. It is based on FaderShell, written by Tom Dowdy.
  6.     This code is hereby placed into the public domain. Use it as a template to write your faders!
  7.         
  8.     You can use global variables, they are stored as an offset from A4 using THINK's SetupA4/RestoreA4 procedures.
  9.     
  10.     This fader also demonstrates the use of callback routines by playing sound. Note that in order to play sound, you must
  11.     request a sound channel from DarkSide by creating a 'Chnl' 0 resource. 
  12. */
  13.  
  14. #include <Memory.h>
  15. #include <Windows.h>
  16. #include <Dialogs.h>
  17. #include <Errors.h>
  18. #include <ToolUtils.h>
  19. #include <TextUtils.h>
  20.  
  21. #include "Fader.h"                                // include DarkSide's interface
  22.  
  23. #ifdef THINK_C
  24.     #define QUICKDRAWBLACK    (qd.black)
  25.     #define QUICKDRAWWHITE    (qd.white)
  26. #else
  27.     #define QUICKDRAWBLACK    (&qd.black)
  28.     #define QUICKDRAWWHITE    (&qd.white)
  29. #endif
  30.  
  31. // Constants. These are specific to Morpion, you can throw them away if you are writing a new fader.
  32.  
  33. #define    border        2
  34.  
  35. #define    empty        0
  36. #define    player1        1
  37. #define    player2        2
  38.  
  39. // Global variables, referenced using A4. Also Morpion-specific.
  40. QDGlobals    qd;
  41.  
  42. short        xloc, yloc;                        // starting location of the grid
  43. short         xsize, ysize;                        // Width and height of grid
  44. short        xc, yc;                            // Pixel coordinates of topleft corner
  45. Handle        grid, points, possible;    
  46. short        value[5][5];
  47. short        turn;                                // Tells who's up
  48. Boolean        draw, winner;                        // End-of-game flags
  49. short        unit;                                // Size of squares
  50. long        nextDraw;                        // TickCount of the next time to draw
  51.  
  52. // grid is considered as a two dimensional array 0..xsize-1, 0..ysize-1 of chars
  53. // points as an array 0..xsize-1, 0..ysize-1 of shorts
  54. // possible as an array 0..xsize*ysize-1 of points
  55.  
  56. #define    Grid(x, y)        ((char*) (*grid)) [xsize * y + x]
  57. #define    Points(x, y)    ((short*) (*points)) [xsize * y + x]
  58. #define    Possible(x)    ((Point*) (*possible)) [x]
  59.  
  60. void StartGame (void)
  61. {
  62.     long        ix;
  63.     char         *p;
  64.  
  65.     turn = player1;                                                // white plays first
  66.     draw = winner = false;                                        
  67.     for (p = *grid, ix = xsize*ysize; ix; p++, ix--) *p = empty;            // clear the grid
  68. }
  69.  
  70. // Called when fader is starting up, before window has been created.
  71. // We initialize our global variables here.
  72.  
  73. OSErr    PreflightFader(MachineInfoPtr machineInfo, long *minTicks, long *maxTicks)
  74. {    
  75.     Rect        bounds;
  76.     long        gridsize;
  77.     short    a, b;
  78.     
  79.     *minTicks = 1;                                    // Tell DarkSide how often we want to be called.
  80.     *maxTicks = 15;
  81.     
  82.     BlockMove(machineInfo->applicationQD, &qd, sizeof(qd));
  83.     
  84.     // The rest is specific to Morpion.
  85.     
  86.     unit = machineInfo->faderSettings->theShorts[1];                    // read the settings
  87.     bounds = machineInfo->theScreens[0].bounds;                        // compute the size of our grid
  88.     xloc = bounds.left;
  89.     yloc = bounds.top;
  90.     xsize = (bounds.right - bounds.left - 6 * unit) / unit;
  91.     ysize = (bounds.bottom - bounds.top - 6 * unit) / unit;
  92.     xc = bounds.left + (bounds.right - bounds.left - unit * xsize) / 2;
  93.     yc = bounds.top + (bounds.bottom - bounds.top - unit * ysize) / 2;
  94.     
  95.     grid = BestNewHandle (gridsize = xsize * ysize);
  96.     if (!(grid))                // allocate memory for the grid
  97.         return memFullErr;
  98.     possible = BestNewHandle (4*gridsize);
  99.     if (!(possible)) {                        // allocate memory for the 'possible' array
  100.         DisposeHandle(grid);
  101.         return memFullErr;
  102.     }
  103.     points = BestNewHandle (2*gridsize);
  104.     if (!(points))    {                    // allocate memory for the 'points' array
  105.         DisposeHandle(possible);
  106.         DisposeHandle(grid);
  107.         return memFullErr;
  108.     }
  109.     
  110.     for (a = 0; a < 5; a++)                                        // initialize the 'value' array
  111.         for (b = 0; b < 5; b++)
  112.             value [a][b] = 0;
  113.     value [0][0] = 10; value [0][1] = 20; value [0][2] = 80; value [0][3] = 300; value [0][4] = 2000;
  114.     value [1][0] = 20; value [2][0] = 70; value [3][0] = 400; value [4][0] = 4000;
  115.     
  116.     StartGame();
  117.  
  118.     return noErr;
  119. }
  120.  
  121. // Called when fader is starting up, after window has been created. We usually erase the screens here.
  122. // We then draw the grid.
  123.  
  124. OSErr    InitializeFader(MachineInfoPtr machineInfo)
  125. {
  126.     short        screenIndex;
  127.     short        ix;
  128.  
  129.     // store the local coordinates of our windows
  130.     xloc = machineInfo->theScreens[0].bounds.left;
  131.     yloc = machineInfo->theScreens[0].bounds.top;
  132.  
  133.     PenPat(QUICKDRAWBLACK);                                            // erase the screens.
  134.     for (screenIndex = 0; screenIndex < machineInfo->numScreens; screenIndex++) 
  135.         PaintRect(&machineInfo->theScreens[screenIndex].bounds);
  136.         
  137.     PenPat(QUICKDRAWWHITE);                                            // draw the grid.
  138.     SetOrigin(-xloc, -yloc);
  139.     for (ix = 0; ix <= xsize; ix++) {
  140.         MoveTo (xc + ix * unit, yc);
  141.         Line (0, unit * ysize);
  142.     }
  143.     for (ix = 0; ix <= ysize; ix++) {
  144.         MoveTo (xc, yc + ix * unit);
  145.         Line (unit * xsize, 0);
  146.     }
  147.     SetOrigin(0, 0);
  148.     
  149.     nextDraw = 0;
  150.     
  151.     return noErr;
  152. }
  153.  
  154. // The DoRow and Play routines are Morpion's IA. They are not part of the fader's interface with DarkSide.
  155.  
  156. void DoRow (short bx, short by, short dx, short dy)
  157. {
  158.     short     i;
  159.     short     x, y;
  160.     short     nfriend, nenemy;
  161.     char    g;
  162.     
  163.     for (nfriend = nenemy = 0, x = bx, y = by, i = 5; i; x += dx, y += dy, i--) {
  164.         if ((g = Grid(x, y)) == turn)                                // count how many enemy/friendly markers
  165.             nfriend++;                                        // are present in those five squares
  166.         else if (g != empty)
  167.             nenemy++;
  168.     }
  169.     
  170.     for (x = bx, y = by, i = 5; i; x += dx, y += dy, i--)                    // increment the strategic value of the free squares
  171.         if (Grid(x, y) == empty)                                    // depending on nenemy and nfriendly
  172.             Points(x, y) += value [nfriend][nenemy];
  173. }
  174.  
  175. Point Play (void)
  176. {
  177.     short    ix, iy, npos, max, n, p;
  178.     Point    zero;
  179.     
  180.     zero.h = zero.v = 0;
  181.     for (ix = 0; ix < xsize; ix++)                                    // Only allow empty spots to be played
  182.         for (iy = 0; iy < ysize; iy++)
  183.             Points(ix, iy) = Grid(ix, iy) == empty ? 0 : -1;
  184.                 
  185.     for (ix = 0; ix < xsize-4; ix++)                                    // Walk the whole grid, giving more points to each
  186.         for (iy = 0; iy < ysize; iy++)                                // interesting spot
  187.             DoRow (ix, iy, 1, 0);
  188.     for (ix = 0; ix < xsize; ix++)
  189.         for (iy = 0; iy < ysize-4; iy++)
  190.             DoRow (ix, iy, 0, 1);
  191.     for (ix = 0; ix < xsize-4; ix++)
  192.         for (iy = 0; iy < ysize-4; iy++)
  193.             DoRow (ix, iy, 1, 1);
  194.     for (ix = 0; ix < xsize-4; ix++)
  195.         for (iy = 4; iy < ysize; iy++)
  196.             DoRow (ix, iy, 1, -1);
  197.  
  198.     for (npos = 0, max = -1, ix = 0; ix < xsize; ix++)                    // Look for the most interesting spot
  199.         for (iy = 0; iy < ysize; iy++) {
  200.             p = Points(ix, iy);
  201.             if (p > max) {                                        // If this square is more interesting than all those
  202.                 max = p; npos = 1;                                // previously visited, forget about them
  203.                 Possible (0).h = ix;
  204.                 Possible (0).v = iy;
  205.             }
  206.             else if (p == max) {                                    // If it is equally interesting than the best previously
  207.                 npos++;                                        // visited squares, add it to the array of possible
  208.                 Possible (npos-1).h = ix;                            // moves.
  209.                 Possible (npos-1).v = iy;
  210.             }
  211.         }
  212.     
  213.     if (max == 0)                                                // Nothing interesting : this is a draw
  214.         draw = true;
  215.     else if (npos) {                                                
  216.         n = Random();                                            // Randomly choose a square from the list
  217.         if (n < 0) n = -n;                                        // of best possible squares
  218.         n = n % npos;
  219.  
  220.         if (max >= value [4][0])                                    // This means we just won
  221.             winner = true;
  222.  
  223.         return Possible (n);                                        // Pick up a solution randomly from the set of
  224.     }                                                        // the best solutions
  225.     else
  226.         draw = true;                                            // The grid is full
  227.         
  228.     return zero;
  229. }
  230.  
  231. // Called regularly. This is the heart of the fader. Here we do whatever we please...
  232.  
  233. OSErr    IdleFader(MachineInfoPtr machineInfo)
  234. {
  235.     Point            location;
  236.     Rect            r;
  237.     unsigned long    finalTicks;
  238.     short        a;
  239.     
  240.     // if it isn't time to draw yet, we return -- we do this rather than delay to avoid
  241.     // locking up the machine with a function as mundane as a screensaver
  242.     if (TickCount() < nextDraw)
  243.         return(noErr);
  244.  
  245.     
  246.     location = Play();                                            // find out where we want to play
  247.  
  248.     if (!draw) {                                                // If the player actually played something
  249.         SetOrigin(-xloc, -yloc);
  250.         r.left = xc + border + unit * location.h;
  251.         r.top = yc + border + unit * location.v;
  252.         r.right = r.left + unit - 2 * border + 1;
  253.         r.bottom = r.top + unit - 2 * border + 1;
  254.         if (turn == player1)
  255.             EraseOval (&r);                                    // draw the new marker in the proper color
  256.         else {
  257.             PenPat (QUICKDRAWWHITE);
  258.             FrameOval (&r);
  259.             PenPat (QUICKDRAWBLACK);
  260.         }
  261.         
  262.         (void) PlayResourceSnd (machineInfo, 128, true);                // play sound asynchronously
  263.         for (a = 4; a; a--) {                                        // make it blink so that the user sees it
  264.             InvertRect (&r);
  265.             Delay (2, &finalTicks);
  266.         }
  267.         
  268.         SetOrigin(0, 0);
  269.     }
  270.  
  271.     // calculate the next time to draw    
  272.     nextDraw = TickCount() + 60 - machineInfo->faderSettings->theShorts[0];
  273.  
  274.     if (draw || winner) {                                            // if the game's over
  275.         Str255    resultString;
  276.         
  277.         PenPat (QUICKDRAWBLACK);
  278.         TextMode (srcXor);
  279.         r = machineInfo->theScreens[0].bounds;
  280.         MoveTo (r.left+2*unit, r.top + 2*unit);                        // print a message explaining why
  281.         if (draw)
  282.             {
  283.             GetIndString(resultString, 5000, 1);
  284.             }
  285.         else 
  286.             {
  287.             if (turn == player1)
  288.                 GetIndString(resultString, 5000, 2);
  289.             else
  290.                 GetIndString(resultString, 5000, 3);
  291.             }
  292.         DrawString(resultString);
  293.         PenNormal();
  294.         
  295.         Delay (240, &finalTicks);                                    // wait a few seconds
  296.         StartGame();                                            // and start a new game
  297.         InitializeFader(machineInfo);
  298.         return noErr;
  299.     }
  300.     
  301.  
  302.     Grid(location.h, location.v) = turn;                                // update the grid in memory
  303.     turn = (turn == player1) ? player2 : player1;                        // change turn
  304.         
  305.     return noErr;
  306. }
  307.  
  308. // Called when the fade is tearing down
  309.  
  310. OSErr    DisposeFader(MachineInfoPtr machineInfo)
  311. {
  312. #pragma unused (machineInfo)
  313.  
  314.     DisposeHandle (grid);                                            // release all the memory we reserved
  315.     DisposeHandle (points);
  316.     DisposeHandle (possible);
  317.     return noErr;
  318. }
  319.  
  320. // Called when there is an update event for our fade window.
  321.  
  322. OSErr    UpdateFader(MachineInfoPtr machineInfo)
  323. {
  324.     InitializeFader(machineInfo);                                    // erase the screen, draw the grid
  325.     return noErr;
  326. }
  327.  
  328. // Called when there is an events in the settings dialog. itemHit will be the item the user has selected, or 0 when the dialog
  329. // is being set up. 
  330. // itemHit - itemOffset will allow you to determine which item in your dialog list this corresponds to.
  331. // If you don't wish to do any special processing of this event, simply return fnfErr and the standard effect will take place.
  332.  
  333. OSErr    HitFader(MachineInfoPtr machineInfo, DialogPtr dPtr, short itemHit, short itemOffset)
  334. {
  335. #pragma unused (machineInfo, dPtr, itemHit, itemOffset)
  336.  
  337.     return fnfErr;
  338. }
  339.